#pragma once

#include "pch.h"

#include "Exercise.h"
#include "Shader/Shader.h"
#include <filesystem>

class Exercise_1 : public Exercise
{
public:
    virtual void run() override
    {
        // 第一步：初始化 GLFW
        glfwInit();
        glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
        glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
        glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

        // 第二步：创建 GLFW window 对象
        GLFWwindow* window = glfwCreateWindow(800, 600, "LearnGL", NULL, NULL);
        if (window == NULL)
        {
            std::cout << "Failed to create GLFWwindow." << std::endl;
            glfwTerminate();
            return;
        }

        // 第三步：设置上下文
        glfwMakeContextCurrent(window);

        // 第四步：初始化 glad
        if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
        {
            std::cout << "Failed to initialize GLAD" << std::endl;
            return;
        }

        std::filesystem::path curPath = std::filesystem::current_path();
        auto path = curPath / "assets" / "shaders" / "core";

        auto vsFilePath = path / "basic.vert";
        auto fsFilePath = path / "basic.frag";
        auto shader = ym::Shader(vsFilePath, fsFilePath);

        int nrAttributes;
        glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &nrAttributes);
        std::cout << "Maximum nr of vertex attributes supported: " << nrAttributes << std::endl;

        // 第五步：设置视口尺寸
        glViewport(0, 0, 800, 600);

        // 第六步：设置回调函数（可选）
        glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

        unsigned int VAO;
        glGenVertexArrays(1, &VAO);
        glBindVertexArray(VAO);

        // clang-format off
        float vertices[] = { 
            // 位置             // 颜色
            -0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f,
             0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f,
             0.0f,  0.5f, 0.0f, 0.0f, 0.0f, 1.0f,
        };
        unsigned int indices[] = {
            0, 1, 2,
            0, 2, 3,
        };
        // clang-format on

        // Vertex Buffers Object
        unsigned int VBO;
        glGenBuffers(1, &VBO);

        glBindBuffer(GL_ARRAY_BUFFER, VBO);
        /**
         * @param 参数1：目标缓冲的类型
         * @param 参数2：缓冲的大小（以字节为单位）
         * @param 参数3：实际发送的缓冲数据
         * @param 参数4：缓冲数据的 Usage
         * @note 缓冲数据的 Usage 参数说明
         * @param GL_STATIC_DRAW：数据不会或几乎不会改变
         * @param GL_DYNAMIC_DRAW：数据会被改变很多
         * @param GL_STREAM_DRAW：数据每次绘制时都会改变
         */
        glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

        // 位置属性
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
        glEnableVertexAttribArray(0);
        // 颜色属性
        glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
        glEnableVertexAttribArray(1);

        // 另一组 VAO VBO
        unsigned int vertexArray;
        glGenVertexArrays(1, &vertexArray);
        glBindVertexArray(vertexArray);

        float vertices_another[] = {
            -0.5f, -0.5f, 0.0f, 0.5f, -0.5f, 0.0f, 0.0f, 0.1f, 0.0f,
        };
        unsigned int vertexBuffer;
        glGenBuffers(1, &vertexBuffer);
        glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
        glBufferData(GL_ARRAY_BUFFER, sizeof(vertices_another), vertices_another, GL_STATIC_DRAW);

        unsigned int EBO;
        glGenBuffers(1, &EBO);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);

        glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

        // 第 1 个参数指定我们要配置的顶点属性。
        // 还记得我们在顶点着色器中使用layout(location = 0)定义了position顶点属性的位置值(Location)吗？
        // 它可以把顶点属性的位置值设置为0。
        // 因为我们希望把数据传递到这一个顶点属性中，所以这里我们传入0。
        // 第 2 个参数指定顶点属性的大小。顶点属性是一个vec3，它由3个值组成，所以大小是3。
        // 第 3 个参数指定数据的类型，这里是GL_FLOAT(GLSL中vec * 都是由浮点数值组成的)。
        // 第 4 个参数定义我们是否希望数据被标准化(Normalize)。
        // 如果我们设置为GL_TRUE，所有数据都会被映射到0（对于有符号型signed数据是 -1）到1之间。我们把它设置为GL_FALSE。
        // 第 5 个参数叫做步长(Stride)，它告诉我们在连续的顶点属性组之间的间隔。
        // 由于下个组位置数据在3个float之后，我们把步长设置为3 * sizeof(float)。
        // 要注意的是由于我们知道这个数组是紧密排列的（在两个顶点属性之间没有空隙）
        // 我们也可以设置为 0,来让OpenGL决定具体步长是多少（只有当数值是紧密排列时才可用）。
        // 一旦我们有更多的顶点属性，我们就必须更小心地定义每个顶点属性之间的间隔，我们在后面会看到更多的例子
        // （译注: 这个参数的意思简单说就是从这个属性第二次出现的地方到整个数组0 位置之间有多少字节）。
        // 最后一个参数的类型是void*，所以需要我们进行这个奇怪的强制类型转换。它表示位置数据在缓冲中起始位置的偏移量(Offset)。由于位置数据在数组的开头，所以这里是0。我们会在后面详细解释这个参数。
        /**
         * @brief 告诉OpenGL如何解析顶点数据
         * @param index 指定要配置的顶点属性的位置
         * @param size 顶点属性的大小
         * @param type 顶点属性的数据类型
         * @param normalized 是否进行标准化
         * @param stride 步长
         * @param pointer 指定位置数据在缓冲中起始位置的偏移量
         * @note 函数原型：void glVertexAttribPointer(GLuint index, GLint size,
         * GLenum type, GLboolean normalized, GLsizei stride, const GLvoid * pointer);
         */
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
        glEnableVertexAttribArray(0);

        // 第七步：进入渲染循环
        while (!glfwWindowShouldClose(window))
        {
            processInput(window);

            glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
            glClear(GL_COLOR_BUFFER_BIT);

            // 设置绘制模式
            // GL_LINE：线框模式
            // GL_FILL：填充模式
            glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

            shader.use();

            // 更新 uniform 颜色
            float timeValue = glfwGetTime();
            float redValue = std::cos(timeValue) / 2.0f + 0.5f;
            float greenValue = std::sin(timeValue) / 2.0f + 0.5f;
            float blueValue = (redValue + greenValue) / 2.0f;
            auto vertexColorLocation = glGetUniformLocation(shader.getID(), "ourColor");
            if (vertexColorLocation != -1)
                glUniform4f(vertexColorLocation, redValue, greenValue, blueValue, 1.0f);

            glBindVertexArray(VAO);
            glDrawArrays(GL_TRIANGLES, 0, 3);

            // glUseProgram(shaderProgram2);
            // glBindVertexArray(vertexArray);
            // glDrawArrays(GL_TRIANGLES, 0, 3);
            //   glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

            glfwSwapBuffers(window);
            glfwPollEvents();
        }

        // 第八步：退出GLFW并清理
        glfwTerminate();
    }

private:
    /** 监听输入 */
    void processInput(GLFWwindow* window)
    {
        if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        {
            glfwSetWindowShouldClose(window, true);
        }
    }

    static void framebuffer_size_callback(GLFWwindow* window, int width, int height)
    {
        std::cout << "framebuffer_size_callback: " << width << ", " << height << std::endl;
        glViewport(0, 0, width, height);
    }
};
